﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.IO.Compression;

using System.Runtime.Remoting;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Lifetime;


namespace RemoteObject
{
    public class UploadClient
    {
        public TransferParam param = new TransferParam();

        List<Common.BlockBufferInfo> bbiToSaveList = new List<Common.BlockBufferInfo>();

        long clientID;
        int startPos, remainSaveDataLength;

        bool saveFileError = false;
        String fnLocal;

        AutoResetEvent saveNextBlockEvent;

        public void StartUpload(String _fnLocal, int offset, int length, long _clientID)
        {
            fnLocal = _fnLocal;
            clientID = _clientID;
            startPos = offset;
            remainSaveDataLength = length;

            saveNextBlockEvent = new AutoResetEvent(false);
            Thread thread = new Thread(new ThreadStart(this.SaveFileBlockProc));
            thread.Start();
        }

        Boolean SaveFileBlockProcStopped = true;
        private void SaveFileBlockProc()
        {
            SaveFileBlockProcStopped = false;
            try
            {
                _SaveFileBlockProc();
            }
            catch
            {
            }
            SaveFileBlockProcStopped = true;
            RemoteObjectDownloader.RemoveClient(clientID);
        }
        private void _SaveFileBlockProc()
        {
            FileStream fs = null;
            try
            {
                fs = new FileStream(fnLocal, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                fs.Seek(startPos, SeekOrigin.Begin);
            }
            catch
            {
                if (fs != null)
                {
                    fs.Close();
                    fs = null;
                }
            }

            while ( fs != null )
            {
                //wait notification for next block saving.
                double timeout_in_minutes = 15;
                if (false == saveNextBlockEvent.WaitOne((int)(1000 * 60 * timeout_in_minutes)))
                {
                    //no response for nMinutes, then kill this client
                    break;
                }

                //read ONE block data to buff
                lock (this)
                {
                    List<Common.BlockBufferInfo> bbiToSaveListNow = new List<Common.BlockBufferInfo>();
                    lock (bbiToSaveList)
                    {
                        foreach (Common.BlockBufferInfo bbi in bbiToSaveList)
                        {
                            bbiToSaveListNow.Add(bbi);
                        }
                        bbiToSaveList.Clear();
                    }

                    Boolean stopMe = false;
                    foreach (Common.BlockBufferInfo bbi in bbiToSaveListNow)
                    {
                        if (bbi == Common.endingBlockBuffer)
                        {
                            stopMe = true;
                        }
                        else if( !saveFileError )
                        {
                            int dataLength = 0;
                            try
                            {
                                if (bbi.dataCompressed)
                                {
                                    Byte[] blockToSave = RemoteObject.MyUtility.DeCompression(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                                    //The final file write --------------------------------------------------
                                    fs.Write(blockToSave, 0, blockToSave.Length);
                                    fs.Flush();
                                    //The final file write --------------------------------------------------
                                    dataLength = blockToSave.Length;
                                }
                                else
                                {
                                    //The final file write --------------------------------------------------
                                    fs.Write(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                                    fs.Flush();
                                    //The final file write --------------------------------------------------
                                    dataLength = bbi.dataSize;
                                }
                                lock (bbiToSaveList)
                                {
                                    remainSaveDataLength -= dataLength;
                                }
                            }
                            catch
                            {
                                saveFileError = true;
                            }
                        }
                        Common.releaseBlock(bbi);
                    }
                    if (stopMe)
                        break;
                }
            }

            if (fs != null)
            {
                fs.Close();
                fs = null;
            }
        }

        public int WaitForFileWriteProcToStop()
        {
            while (!SaveFileBlockProcStopped )
            {
                lock (bbiToSaveList)
                {
                    bbiToSaveList.Add(Common.endingBlockBuffer);
                    saveNextBlockEvent.Set(); //ask WriteFileProc to quit
                }
                Thread.Sleep(300);
            }

            RemoteObjectDownloader.RemoveClient(clientID);
            return remainSaveDataLength;
        }

        public int MoveReceivedDataToBufferToSaveList( Byte[] fileData, Boolean compressed )
        {
            if (saveFileError || fileData.Length > Common.BlockSize )
            {
                return -1;
            }

            //Slowdown the speed
            Thread.Sleep(100);

            Common.BlockBufferInfo bbiToSave = Common.getFreeBlockOnWait(0);

            //copy the fileData to bbiToSave for saving.
            Buffer.BlockCopy(fileData, 0, bbiToSave.buffer, bbiToSave.bufferStartPos, fileData.Length);
            bbiToSave.dataSize = fileData.Length;
            bbiToSave.dataCompressed = compressed;
            lock (bbiToSaveList)
            {
                bbiToSaveList.Add(bbiToSave);
                //fire save event.
                saveNextBlockEvent.Set();
                return remainSaveDataLength;
            }
        }
    }

    public class RemoteObjectUploader : BaseRemoteObject
    {
        //------------------------------------------------------------------------------------------------
        public static long clientID = 0;
        public static Hashtable uploadClientList = new Hashtable();

        public long GetNewClientID()
        {
            lock (uploadClientList)
            {
                return ++clientID;
            }
        }

        static public void RemoveClient(long clientID)
        {
            uploadClientList.Remove(clientID);
        }

        public long PrepareUploadFile( String fileName, long fileLength )
        {
            String fnLocal = GetLocalFilePath(fileName);
            if (!File.Exists(fnLocal))
            {
                try
                {
                    FileStream fs = new FileStream(fnLocal, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fs.SetLength(fileLength);
                    fs.Close();
                }
                catch
                {
                    File.Delete(fnLocal);
                    return -1;
                }
            }
            else
            {
                FileInfo fileInfo = new FileInfo(fnLocal);
                if (fileInfo.Length != fileLength)
                {
                    try
                    {
                        File.Delete(fnLocal);
                        FileStream fs = new FileStream(fnLocal, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
                        fs.SetLength(fileLength);
                        fs.Close();
                    }
                    catch
                    {
                        File.Delete(fnLocal);
                        return -1;
                    }
                }
            }
            return 1;
        }

        static String GetLocalFilePath( String fileName )
        {
            return Common.LocalPath.TrimEnd('\\') + "\\" + fileName;
        }

        public long StartUploadFile(String fileName, long clientID, int startPos, int dataLength, TransferParam param)
        {
            String fnLocal = GetLocalFilePath(fileName);
            if (!File.Exists(fnLocal))
                return -1;

            FileInfo fileInfo = new FileInfo(fnLocal);
            if (startPos + dataLength > fileInfo.Length)
                return -1;

            if (uploadClientList.Contains(clientID))
            {
                UploadClient ci = (UploadClient)uploadClientList[clientID];
                ci.param = param;
                ci.StartUpload(fnLocal, startPos, dataLength, clientID);
            }
            else
            {
                //create new Client and put it in gloal hashtable.
                UploadClient newCI = new UploadClient();
                newCI.param = param;
                newCI.StartUpload(fnLocal, startPos, dataLength, clientID);
                uploadClientList.Add(clientID, newCI);
            }

            return 1;
        }

        public int StopUploadFile(long clientID)
        {
            if (!uploadClientList.Contains(clientID))
                return -1;

            UploadClient ci = (UploadClient)uploadClientList[clientID];
            return ci.WaitForFileWriteProcToStop();
        }

        public int SendingFileData(long clientID, Boolean compressed, Byte[] fileData )
        {
            UploadClient ci;
            if (uploadClientList.Contains(clientID))
            {
                ci = (UploadClient)uploadClientList[clientID];
                return ci.MoveReceivedDataToBufferToSaveList(fileData, compressed);
            }
            return -1;
        }
    }
}
